package com.freestyle.multidatasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.freestyle.common.spring.support.GlobalConfig;
import com.freestyle.common.spring.utils.MybatisUtil;
import com.freestyle.common.spring.utils.StringUtils;
import com.freestyle.common.tkmybatis.MyMapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.transaction.TransactionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.*;

/**
 * Created by rocklee on 2020/8/27 10:46
 */

public class DataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
  private final Logger logger = LoggerFactory.getLogger("DynamicDataSource");
  private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
  //private ConversionService conversionService = new DefaultConversionService();
  //private static Map sourcePoolMap = new HashMap();
  private static  DataSource defaultDataSource;
  private final static Map<String, DataSource> customDataSources = Collections.synchronizedMap(new HashMap<>());
  //private static Map<String, PlatformTransactionManager> customTransactionManager = new HashMap();
  private GlobalConfig config;
  private PlatformTransactionManager defaultTransactionManager;
  private static DataSourceRegister self;
  private String defaultTransactionManagerName;


  public DataSourceRegister() {
    self=this;
  }


  public static PlatformTransactionManager getDefaultTransactionManager() {
    return self.defaultTransactionManager;
  }

  public static Map<String, DataSource> getCustomDataSources() {
    return customDataSources;
  }

  @Deprecated
  public static void setDefaultTransactionManager(String beanName, PlatformTransactionManager defaultTransactionManager) {
    self.defaultTransactionManagerName=beanName;
    //customTransactionManager.put(beanName,defaultTransactionManager);
    self.defaultTransactionManager = defaultTransactionManager;
  }

  public static DataSource getDefaultDataSource() {
    return defaultDataSource;
  }
  public static DataSource getCustomDataSource(String dataSourceId){
    if (dataSourceId==null||dataSourceId.isEmpty()) return defaultDataSource;
    return customDataSources.get(dataSourceId);
  }

  /**
   * 为mapper切换datasource 和Transactionmanager
   * @param mapper
   * @param dataSourceName null为默认datasource
   */
  public static void switchDataSource(MyMapper mapper, String dataSourceName){
    DataSource dataSource=null;
    if (dataSourceName==null){
      dataSource=getDefaultDataSource();
    }
    else{
      dataSource=getCustomDataSource(dataSourceName);
    }
    SqlSession ss= MybatisUtil.getSqlSessionFromMapperInstance(mapper);

    Environment ev=ss.getConfiguration().getEnvironment();
    TransactionFactory tf= ev.getTransactionFactory();
    for (Field f: tf.getClass().getDeclaredFields()){
      try {
        Object o= f.get(tf);
        System.out.println(f.getName()+"="+o);
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
  }
  @Deprecated
  public static PlatformTransactionManager getTransactionManager(){
    String dsT=DataSourceContextHolder.getDataSource();
    String defaultDataSourceName=self.config.getValue("spring.default-datasource");
    if (dsT==null||dsT.equalsIgnoreCase(defaultDataSourceName)){
      return self.defaultTransactionManager;
    }
    else{
      return TransactionManagerRegister.getTransactionManager("tm_"+dsT);
      //return ContextUtil.getBean("tm_"+dsT,PlatformTransactionManager.class);
    }
  }

  @Override
  public void setEnvironment(org.springframework.core.env.Environment environment) {
    logger.debug("DynamicDataSourceRegister.setEnvironment()");
    config= new GlobalConfig(environment);
    initDefaultDataSource(environment);
    initCustomdatasources(environment);
  }

  private void initDefaultDataSource(org.springframework.core.env.Environment env) {

    if (!config.containsProperty("spring.default-datasource")){
      logger.error(String.format("Missing config '%s'","spring.default-datasource"));
      return;
    }
    String defaultDataSourceName=config.getValue("spring.default-datasource");
    Map<String,Object> paramMap=config.getProperties("spring.datasources."+defaultDataSourceName,true);
    if (paramMap.size()==0){
      logger.error(String.format("Missing config '%s'","spring.datasources."+defaultDataSourceName));
      return;
    }
    String url=config.getValue(String.format("spring.datasources.%s.url",defaultDataSourceName));
    if (!StringUtils.isEmpty(url)){
      paramMap.put("url",url);
    }
    Map<String, Object> dsMap = new HashMap();
    dsMap.put("type", paramMap.get("type"));
    dsMap.put("driverClassName", paramMap.get("driver-class-name"));
    dsMap.put("url", paramMap.get("url"));
    dsMap.put("username", paramMap.get("username"));
    dsMap.put("password", paramMap.get("password"));
    paramMap=config.getProperties("spring.datasources."+defaultDataSourceName+".druid",true);
    dsMap.putAll(paramMap);
    defaultDataSource = buildDataSource(dsMap);
    //dataBinder(defaultDataSource, env);
  }

  private void initCustomdatasources(org.springframework.core.env.Environment env) {
    String defaultSource=config.getValue("spring.default-datasource");
    List<String> dsSessions = config.getSessions("spring.datasources",true);
      for (String dsSession : dsSessions) {
        if (dsSession.equals(defaultSource)){
          customDataSources.put(dsSession, defaultDataSource);
          continue;
        }
        Map<String,Object> paramMap=config.getProperties("spring.datasources."+dsSession,true);
        Map<String, Object> dsMap = new HashMap();
        dsMap.put("type", paramMap.get("type"));
        dsMap.put("driverClassName", paramMap.get("driver-class-name"));
        String url=config.getValue(String.format("spring.datasources.%s.url",dsSession));
        if (!StringUtils.isEmpty(url)){
          paramMap.put("url",url);
        }
        dsMap.put("url", paramMap.get("url"));
        dsMap.put("username", paramMap.get("username"));
        dsMap.put("password", paramMap.get("password"));
        paramMap=config.getProperties("spring.datasources."+dsSession+".druid",true);
        dsMap.putAll(paramMap);
        DataSource ds = buildDataSource(dsMap);
        customDataSources.put(dsSession, ds);
        /*if (dsSession.equals(defaultSource)) {
          //customTransactionManager.put("transactionManager", new DataSourceTransactionManager(ds));
        }
        else{
          customTransactionManager.put("tm_"+dsSession, new DataSourceTransactionManager(ds));
        }*/
        //dataBinder(ds, env);
      }
  }

  public DataSource buildDataSource(Map<String, Object> dsMap) {
    Object type = dsMap.get("type");
    if (type == null) {
      type = DATASOURCE_TYPE_DEFAULT;
    }
    try {
      Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
      String driverClassName = dsMap.get("driverClassName").toString();
      String url = dsMap.get("url").toString();
      String username = dsMap.get("username").toString();
      String password = dsMap.get("password").toString();
      DruidDataSource dataSource = new DruidDataSource();

      dataSource.setUrl(url);
      dataSource.setUsername(username);
      dataSource.setPassword(password);
      dataSource.setDriverClassName(driverClassName);

      int initialSize = (Integer)dsMap.get("initialSize");
      int minIdle = (Integer)dsMap.get("minIdle");
      int maxActive = (Integer) dsMap.get("maxActive");
      int maxWait =(Integer) dsMap.get("maxWait");
      int timeBetweenEvictionRunsMillis = (Integer) dsMap.get("timeBetweenEvictionRunsMillis");
      int minEvictableIdleTimeMillis = (Integer) dsMap.get("minEvictableIdleTimeMillis");
      String validationQuery = (String) dsMap.get("validationQuery");
      boolean testWhileIdle = (Boolean) dsMap.getOrDefault("testWhileIdle",true);
      boolean testOnBorrow = (Boolean) dsMap.getOrDefault("testOnBorrow",false);
      boolean testOnReturn =(Boolean) dsMap.getOrDefault("testOnReturn",true);
      boolean poolPreparedStatements = (boolean)dsMap.getOrDefault("poolPreparedStatements",true);
      int maxPoolPreparedStatementPerConnectionSize = (Integer) dsMap.getOrDefault("maxPoolPreparedStatementPerConnectionSize",20);
      String filters = (String) dsMap.get("filters");
      String connectionProperties = (String) dsMap.get("connectionProperties");
      boolean useGlobaldatasourcestat = (boolean) dsMap.getOrDefault("useGlobaldatasourcestat",true);

      dataSource.setInitialSize(initialSize);
      dataSource.setMinIdle(minIdle);
      dataSource.setMaxActive(maxActive);
      dataSource.setMaxWait(maxWait);
      dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
      dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
      dataSource.setValidationQuery(validationQuery);
      dataSource.setTestWhileIdle(testWhileIdle);
      dataSource.setTestOnBorrow(testOnBorrow);
      dataSource.setTestOnReturn(testOnReturn);
      dataSource.setPoolPreparedStatements(poolPreparedStatements);
      dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
      dataSource.setFilters(filters);
      Properties properties = new Properties();
      for (String item : connectionProperties.split(";")) {
        String[] attr = item.split("=");
        properties.put(attr[0].trim(), attr[1].trim());
      }
      dataSource.setConnectProperties(properties);
      dataSource.setUseGlobalDataSourceStat(useGlobaldatasourcestat);
      return dataSource;
    } catch (RuntimeException e) {
      logger.error("初始化数据源出错!");
      e.printStackTrace();
    } catch (Exception e) {
      logger.error("初始化数据源出错!");
      e.printStackTrace();
    }
    return null;
  }


  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    logger.debug("DynamicDataSourceRegister.registerBeanDefinitions()");
    Map<Object, DataSource> targetdatasources = new HashMap<>();

    targetdatasources.put("dataSource", defaultDataSource);

    DataSourceContextHolder.getDataSourceIds().add("dataSource");

    targetdatasources.putAll(customDataSources);
    for (String key : customDataSources.keySet()) {
      DataSourceContextHolder.getDataSourceIds().add(key);
    }

    //注册MultiDatasource
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClass(MultiDataSource.class);

    beanDefinition.setSynthetic(true);
    MutablePropertyValues mpv = beanDefinition.getPropertyValues();
    /*myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);*/
    mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
    mpv.addPropertyValue("targetDataSources", targetdatasources);
    registry.registerBeanDefinition("dataSource", beanDefinition);

    /*MutablePropertyValues attrVals=null;
    if (registry.containsBeanDefinition("transactionProxyFactoryBean")){
      //org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader a;
      BeanDefinition b= (BeanDefinition) registry.getBeanDefinition("transactionProxyFactoryBean");
      attrVals=b.getPropertyValues();
      registry.removeBeanDefinition("transactionProxyFactoryBean");
    }
    GenericBeanDefinition beanDef = new GenericBeanDefinition();
    beanDef.setBeanClass(TransactionProxyFactoryBeanEx.class);
    beanDef.setSynthetic(true);
    beanDef.addQualifier(new AutowireCandidateQualifier(TransactionProxyFactoryBeanEx.class,"transactionProxyFactoryBean"));
    beanDef.setPropertyValues(attrVals);
    registry.registerBeanDefinition("transactionProxyFactoryBean", beanDef);
*/


    for (String key : customDataSources.keySet()) {
      GenericBeanDefinition beanDef = new GenericBeanDefinition();
      beanDef.setBeanClass(DataSourceTransactionManager.class);
      beanDef.setSynthetic(true);
      beanDef.addQualifier(new AutowireCandidateQualifier(DataSourceTransactionManager.class,"tm_"+key));
      mpv = beanDef.getPropertyValues();
      DataSource ds= customDataSources.get(key);
      ((DruidDataSource)ds).setDefaultAutoCommit(false);
      mpv.addPropertyValue("dataSource",ds);
      mpv.addPropertyValue("nestedTransactionAllowed", true);
      registry.registerBeanDefinition("tm_"+key, beanDef);
    }

    /*GenericBeanDefinition beanDef = new GenericBeanDefinition();
    beanDef.setBeanClass(MultiDataSourceTransactionManager.class);
    beanDef.setSynthetic(true);
    //mpv = beanDef.getPropertyValues();
    registry.registerBeanDefinition("transactionManager", beanDef);*/
  }
}
